package cn.jimjim.framework.code.generator;

import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.jooq.codegen.GenerationTool;
import org.jooq.meta.jaxb.Configuration;
import org.jooq.meta.jaxb.Generator;
import org.jooq.meta.jaxb.Jdbc;
import org.jooq.tools.JooqLogger;

import java.io.File;
import java.io.FileInputStream;
import java.sql.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;

/**
 * @author jim
 * @date 2019-03-12
 */
public class CodeGenerator{

    private static JooqLogger logger =  JooqLogger.getLogger(CommonPageParser.class);

    private ConcurrentHashMap<String, ClassDefinition> tableClassDefinitionMap = new ConcurrentHashMap<>();

    /** 所有表的标识符 */
    private static final String ALL_TABLES_FLAG = ".*";

    private static final List<String> IGNORE_COLUMNS = Arrays.asList("creator", "updater", "is_deleted", "create_time", "update_time", "version");

    public void generate(final File jooqConfig) {
        try {
            Configuration configuration = loadConfig(jooqConfig);
            //调用jooq生成所需文件
            GenerationTool.generate(configuration);
            //初始化类描述信息
            createTableDesc(configuration);
            //创建文件
            generateFile(configuration);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 读取jooq的配置文件
     */
    private Configuration loadConfig(File jooqConfig) throws Exception {
        FileInputStream in = new FileInputStream(jooqConfig);
        return GenerationTool.load(in);
    }

    /**
     * 创建表描述信息
     *
     * @param configuration 配置信息
     */
    private void createTableDesc(Configuration configuration) throws SQLException, ClassNotFoundException {

        Jdbc jdbc = configuration.getJdbc();
        Generator generator = configuration.getGenerator();
        String database = generator.getDatabase().getInputSchema();
        String tables = generator.getDatabase().getIncludes();
        String packageName = generator.getTarget().getPackageName();
        StringBuilder sqlColumns = new StringBuilder("SELECT TABLE_SCHEMA,TABLE_NAME,TABLE_COMMENT " +
                "FROM information_schema.`TABLES` " +
                "WHERE table_schema = '" + database + "' ");
        if (!tables.contentEquals(ALL_TABLES_FLAG)) {
            String tableName = tables.replace("|","','");
            sqlColumns.append(" AND table_name IN ('").append(tableName).append("')");
        }

        Connection con = DriverManager.getConnection(jdbc.getUrl(), jdbc.getUser(), jdbc.getPassword());
        PreparedStatement ps = con.prepareStatement(sqlColumns.toString());
        ResultSet rs = ps.executeQuery();
        ClassDefinition classDefinition;
        while (rs.next()) {
            classDefinition = new ClassDefinition();
            classDefinition.setPackageName(packageName);
            String className = getClassName(rs.getString("TABLE_NAME"));
            classDefinition.setClassName(className);
            classDefinition.setComment(rs.getString("TABLE_COMMENT"));
            classDefinition.setFieldDefinitions(new ArrayList<>());
            classDefinition.setPrimaryKeys(new ArrayList<>());
            tableClassDefinitionMap.put(className, classDefinition);
        }
        rs.close();
        ps.close();

        createFieldDefinition(configuration, con);
        con.close();
    }


    /**
     * 创建属性描述信息
     *
     * @param configuration 配置信息
     */
    private void createFieldDefinition(Configuration configuration, Connection con) throws SQLException, ClassNotFoundException {

        Generator generator = configuration.getGenerator();
        String tables = generator.getDatabase().getIncludes();
        String database = generator.getDatabase().getInputSchema();

        StringBuilder sqlColumns = new StringBuilder("SELECT DISTINCT TABLE_NAME, COLUMN_NAME, DATA_TYPE, COLUMN_COMMENT, COLUMN_KEY, " +
                "CHARACTER_MAXIMUM_LENGTH, IS_NULLABLE, COLUMN_DEFAULT,COLUMN_TYPE, ORDINAL_POSITION " +
                "FROM information_schema.columns " +
                "WHERE table_schema = '" + database + "'");
        if (!tables.contentEquals(ALL_TABLES_FLAG)) {
            String tableName = tables.replace("|","','");
            sqlColumns.append(" AND table_name IN ('").append(tableName).append("')");
        }

        PreparedStatement ps = con.prepareStatement(sqlColumns.toString());
        ResultSet rs = ps.executeQuery();
//		支持联合主键
        while (rs.next()) {
            String tableName = getClassName(rs.getString("TABLE_NAME"));
            ClassDefinition classDefinition = tableClassDefinitionMap.get(tableName);
            FieldDefinition fieldDefinition = new FieldDefinition();
//          处理is开头字段，根据阿里开发规范去掉is
            String fieldName = rs.getString("COLUMN_NAME");
            if (StringUtils.startsWith(fieldName, "is_")) {
                fieldName = StringUtils.replaceOnce(fieldName, "is_", "");
            }
            fieldDefinition.setColumnName(rs.getString("COLUMN_NAME"))
                    .setColumnType(rs.getString("DATA_TYPE"))
                    .setFieldName(getFieldName(fieldName))
                    .setFieldTypeName(getType(rs.getString("DATA_TYPE"), fieldName))
                    .setComment(rs.getString("COLUMN_COMMENT"))
                    .setIsPrimaryKey("PRI".equals(rs.getString("COLUMN_KEY")))
                    .setColumnLength(rs.getInt("CHARACTER_MAXIMUM_LENGTH"))
                    .setNullable("NO".equals(rs.getString("IS_NULLABLE")));
//          enum类型的字段需要动态创建
            if ("enum".equalsIgnoreCase(fieldDefinition.getColumnType())) {
                fieldDefinition.setEnumClassName(getClassName(fieldDefinition.getFieldName()) + "Enum")
                        .setEnumValues(
                                getEnumValues(fieldDefinition.getColumnName(), rs.getString("COLUMN_TYPE"),
                                        fieldDefinition.getComment()))
                        .setFieldTypeFullName(StringUtils.join(classDefinition.getPackageName(), ".enums.",
                                fieldDefinition.getFieldTypeName()));


            }
            if (IGNORE_COLUMNS.contains(fieldDefinition.getColumnName())) {
                continue;
            }
//          设置主键
            if (fieldDefinition.getIsPrimaryKey()) {
                classDefinition.getPrimaryKeys().add(fieldDefinition);
            }
            classDefinition.getFieldDefinitions().add(fieldDefinition);
        }
        rs.close();
        ps.close();

        con.close();
    }


    private EnumFieldDefinition[] getEnumValues(String columnName, String columnType, String comment) {
        String[] enumFields = StringUtils.split(StringUtils.substringBetween(columnType, "(", ")")
                .toUpperCase().replace("'", ""), ",");
        List<EnumFieldDefinition> list = new ArrayList<>();
        Map<String, String> enumComments = parseComment(comment);
        if (enumComments == null) {
            logger.warn("'{}'注释格式无法解析，不能生成创建enum注解，注释示例：渠道类型(ALI:某宝,JD:东哥)", columnName);
        }
        for (String enumField : enumFields) {
            list.add(new EnumFieldDefinition().setName(enumField).setAnnotation(MapUtil.getStr(enumComments, enumField)));
        }
        return list.toArray(new EnumFieldDefinition[enumFields.length]);
    }

    /**
     * 解析注释
     * <p>
     * 注释格式 (Y:是,N:否)
     *
     * @param comment 列注释
     * @return 枚举值:注释的map
     */
    private Map<String, String> parseComment(String comment) {
        if (StringUtils.isEmpty(comment)) {
            return null;
        }
        //处理中文符号
        comment = comment.replaceAll("（", "(");
        comment = comment.replaceAll("）", ")");
        comment = comment.replaceAll("，", ",");
        comment = comment.replaceAll("：", ":");
        comment = StringUtils.substringBetween(comment, "(", ")");

        if (StringUtils.isEmpty(comment)) {
            return null;
        }
        String[] enums = StringUtils.split(comment, ",");
        if (enums.length == 1) {
            return null;
        }
        Map<String, String> map = new HashMap<>(enums.length);
        for (String enumString : enums) {
            String[] kv = enumString.split(":");
            if (kv.length == 1) {
                return null;
            }
            map.put(kv[0], kv[1]);
        }
        return map;
    }

    private String getType(String dataType, String columnName) {
        switch (dataType.toLowerCase()) {
            case "int":
                return "Integer";
            case "long":
            case "bigint":
                return "Long";
            case "decimal":
                return "java.math.BigDecimal";
            case "timestamp":
            case "date":
            case "datetime":
                return "java.time.LocalDateTime";
            case "float":
                return "Float";
            case "double":
                return "Double";
            case "tinyint": {
                return "Boolean";
            }
            case "enum": {
                return StringUtils.join(getClassName(columnName), "Enum");
            }
            default:
                return "String";
        }
    }

    private String getClassName(String name) {
        return StrUtil.upperFirst(StrUtil.toCamelCase(name));
//        String[] split = name.split("_");
//        if (split.length > 1) {
//            StringBuilder sb = new StringBuilder();
//            for (String aSplit : split) {
//                String tempTableName = aSplit.substring(0, 1).toUpperCase()
//                        + aSplit.substring(1).toLowerCase();
//                sb.append(tempTableName);
//            }
//            return sb.toString();
//        } else {
//            return split[0].substring(0, 1).toUpperCase() + split[0].substring(1);
//        }
    }


    private String getFieldName(String columnName) {
        return StrUtil.lowerFirst(StrUtil.toCamelCase(columnName));
//        String[] split = columnName.split("_");
//        if (split.length > 1) {
//            StringBuilder sb = new StringBuilder();
//            for (int i = 0; i < split.length; i++) {
//                String tempTableName;
//                if (i == 0) {
//                    tempTableName = split[i].substring(0, 1).toLowerCase() + split[i].substring(1);
//                } else {
//                    tempTableName = split[i].substring(0, 1).toUpperCase() + split[i].substring(1);
//                }
//                sb.append(tempTableName);
//            }
//            return sb.toString();
//        } else {
//            return split[0].substring(0, 1).toLowerCase() + split[0].substring(1);
//        }
    }

    private void generateFile(Configuration configuration) throws Exception {
//        此处使用jooq的配置文件及加载
        Generator generator = configuration.getGenerator();
        for (String className : tableClassDefinitionMap.keySet()) {
            ClassDefinition classDefinition = tableClassDefinitionMap.get(className);

            String lowerName = className.substring(0, 1).toLowerCase() + className.substring(1);

            String doPath = File.separator + "model" + File.separator + className + ".java";

            String criteriaPath = File.separator + "criteria" + File.separator + className + "Criteria.java";

            String daoPath = File.separator + "dao" + File.separator + className + "Dao.java";

            String servicePath = File.separator + "service" + File.separator + className + "Service.java";

            String serviceImplPath = File.separator + "service" + File.separator + "impl" + File.separator + className + "ServiceImpl.java";

            String controllerPath = File.separator + "controller" + File.separator + className + "Controller.java";
            Map<String, Object> context = new HashMap<>(7);
            context.put("className", className);
            context.put("lowerName", lowerName);
            context.put("columnName", StrUtil.toUnderlineCase(lowerName).toUpperCase());
            context.put("table", StrUtil.toUnderlineCase(className).toLowerCase());
            context.put("modulePackage", classDefinition.getPackageName());
            context.put("fields", classDefinition.getFieldDefinitions());
            context.put("priKeys", classDefinition.getPrimaryKeys());
            context.put("userName", "jimjim-code-generator");
            context.put("tableComment", StringUtils.endsWith(classDefinition.getComment(), "表")
                    ? StringUtils.substringBeforeLast(classDefinition.getComment(), "表")
                    : classDefinition.getComment());

            String fileDirPath = configuration.getGenerator().getTarget().getDirectory() + "/" + replaceSeparator(classDefinition.getPackageName());

            CommonPageParser.writerPage(context, "Model.ftl", fileDirPath, doPath);
            CommonPageParser.writerPage(context, "Criteria.ftl", fileDirPath, criteriaPath);
            CommonPageParser.writerPage(context, "Dao.ftl", fileDirPath, daoPath);
            CommonPageParser.writerPage(context, "Service.ftl", fileDirPath, servicePath);
            CommonPageParser.writerPage(context, "ServiceImpl.ftl", fileDirPath, serviceImplPath);
            CommonPageParser.writerPage(context, "Controller.ftl", fileDirPath, controllerPath);
            //生成枚举类型
            for (FieldDefinition fieldDefinition : classDefinition.getFieldDefinitions()) {
                if (!"enum".equals(fieldDefinition.getColumnType())) {
                    continue;
                }
                context.put("fieldDefinition", fieldDefinition);
                String enumPath = File.separator + "enums" + File.separator + getClassName(fieldDefinition.getColumnName()) + "Enum.java";
                CommonPageParser.writerPage(context, "Enum.ftl", fileDirPath, enumPath);
            }
        }
    }

    private String replaceSeparator(String s) {
        return s.replaceAll("\\.", Matcher.quoteReplacement(File.separator));
    }

}
